package com.netkiller.mailmonitor.service;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
import com.google.gdata.client.authn.oauth.GoogleOAuthHelper;
import com.google.gdata.client.authn.oauth.GoogleOAuthParameters;
import com.google.gdata.client.authn.oauth.OAuthException;
import com.google.gdata.client.authn.oauth.OAuthHmacSha1Signer;
import com.netkiller.mailmonitor.core.APP_CONSTANTS;
import com.netkiller.mailmonitor.core.AppException;
import com.netkiller.mailmonitor.entity.AppUser;
import com.netkiller.mailmonitor.entity.OAuthEntity;
import com.netkiller.mailmonitor.vo.AppProperties;

/**
 * service methods needed for oauth authentication
 * @author JKB
 *
 */
@Service
public class OauthService {

	@Autowired
	private AppProperties appProperties;



	public OAuthEntity getAccessToken(String verifier, AppUser appUser) {
		OAuthEntity accessToken = null;
		try {
			OAuthEntity requestToken = new OAuthEntity(appUser.getOauthtoken(),
					appUser.getOauthtokensecret());
			accessToken = phase2(appProperties.getConsumerKey(),
					appProperties.getConsumerKeySecret(), appUser.getEmail(),
					requestToken, verifier);
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return accessToken;
	}

	public OAuthEntity getRequestToken(String email) {
		OAuthEntity requestToken = null;
		try {
			String callbackUrl = appProperties.getApplicationUrl()
					+ APP_CONSTANTS.CALLBACK_PATH;
			requestToken = phase1(appProperties.getMailScope(), callbackUrl,
					appProperties.getConsumerKey(),
					appProperties.getConsumerKeySecret(), email);
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			// throw new AppException(e.getMessage());
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return requestToken;
	}

	static final java.util.logging.Logger logger = java.util.logging.Logger
			.getLogger(OauthService.class.getName());
	static boolean LOGGABLE = logger.isLoggable(java.util.logging.Level.INFO);
	static final String ENC = "utf-8";
	static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";

	/**
	 * @param scope
	 * @param consumerKey
	 * @param consumerSecret
	 * @param email
	 * @return the request token.
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws IOException
	 */
	public static OAuthEntity phase1(String scope, String callbackUrl,
			String consumerKey, String consumerSecret, String email)
			throws java.security.InvalidKeyException,
			java.security.NoSuchAlgorithmException, java.io.IOException {
		OAuthEntity consumer = new OAuthEntity(consumerKey, consumerSecret);
		GoogleAccountsUrlGenerator generator = new GoogleAccountsUrlGenerator(
				email);
		return generateRequestToken(consumer, scope, callbackUrl, -1, -1,
				generator);
	}

	/**
	 * @param consumerKey
	 * @param consumerSecret
	 * @param email
	 * @param requestToken
	 * @param verifier
	 * @return the access token.
	 * @throws IOException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeyException
	 */
	public static OAuthEntity phase2(String consumerKey, String consumerSecret,
			String email, OAuthEntity requestToken, String oauthVerifier)
			throws java.security.InvalidKeyException,
			java.security.NoSuchAlgorithmException, java.io.IOException {
		OAuthEntity consumer = new OAuthEntity(consumerKey, consumerSecret);
		GoogleAccountsUrlGenerator generator = new GoogleAccountsUrlGenerator(
				email);
		return getAccessToken(consumer, requestToken, oauthVerifier, generator);
	}

	static String formatUrlParams(java.util.Map<String, String> params)
			throws java.io.UnsupportedEncodingException {
		StringBuilder b = new StringBuilder();
		boolean first = true;
		for (String key : sorted(params)) {
			if (first == false) {
				b.append('&');
			} else {
				first = false;
			}
			b.append(key).append('=')
					.append(java.net.URLEncoder.encode(params.get(key), ENC));
		}
		return b.toString();
	}

	static String escapeAndJoin(Iterable<String> params)
			throws java.io.UnsupportedEncodingException {
		StringBuilder b = new StringBuilder();
		boolean first = true;
		for (String s : params) {
			if (first == false) {
				b.append('&');
			} else {
				first = false;
			}
			b.append(java.net.URLEncoder.encode(s, ENC));
		}
		return b.toString();
	}

	static String generateSignatureBaseString(String method,
			String requestUrlBase, java.util.Map<String, String> params)
			throws java.io.UnsupportedEncodingException {
		return escapeAndJoin(java.util.Arrays.asList(method, requestUrlBase,
				formatUrlParams(params)));
	}

	static String generateHmacSha1Signature(String data, String key)
			throws java.security.NoSuchAlgorithmException,
			java.security.InvalidKeyException {
		javax.crypto.spec.SecretKeySpec signingKey = new javax.crypto.spec.SecretKeySpec(
				key.getBytes(), HMAC_SHA1_ALGORITHM);
		javax.crypto.Mac mac = javax.crypto.Mac
				.getInstance(HMAC_SHA1_ALGORITHM);
		mac.init(signingKey);
		return byteArrayToBase64(mac.doFinal(data.getBytes()));
	}

	static String generateOauthSignature(String baseString,
			String consumerSecret, String tokenSecret)
			throws java.security.InvalidKeyException,
			java.security.NoSuchAlgorithmException,
			java.io.UnsupportedEncodingException {
		return generateHmacSha1Signature(baseString,
				escapeAndJoin(java.util.Arrays.asList(consumerSecret,
						tokenSecret)));
	}

	static java.util.Map<String, String> parseUrlParamString(String paramString)
			throws java.io.UnsupportedEncodingException {
		String[] pairs = paramString.split("&");
		java.util.Map<String, String> map = new java.util.HashMap<String, String>(
				pairs.length);
		for (String pair : pairs) {
			String[] split = pair.split("=");
			map.put(split[0], java.net.URLDecoder.decode(split[1], ENC));
		}
		return map;
	}

	static class GoogleAccountsUrlGenerator {
		String appsDomain;

		GoogleAccountsUrlGenerator(String email) {
			int indexOf = email.indexOf('@');
			if (indexOf <= 0) {
				return;
			}
			String domain = email.substring(indexOf + 1).toLowerCase();
			if (domain.equals("gmail.com") == false
					&& domain.equals("googlemail.com") == false) {
				appsDomain = domain;
			}
		}

		String getRequestTokenUrl() {
			return "https://www.google.com/accounts/OAuthGetRequestToken";
		}

		String getAuthorizeTokenUrl() {
			if (appsDomain != null) {
				return String.format(
						"https://www.google.com/a/%s/OAuthAuthorizeToken",
						appsDomain);
			} else {
				return "https://www.google.com/accounts/OAuthAuthorizeToken";
			}
		}

		String getAccessTokenUrl() {
			return "https://www.google.com/accounts/OAuthGetAccessToken";
		}
	}

	static void fillInCommonOauthParams(java.util.Map<String, String> params,
			OAuthEntity consumer, long nonce, long timestamp) {
		params.put("oauth_consumer_key", consumer.getKey());
		if (nonce < 0) {
			nonce = (long) (Math.random() * Math.pow(2.0, 64.0));
		}
		params.put("oauth_nonce", String.valueOf(nonce));
		params.put("oauth_signature_method", "HMAC-SHA1");
		params.put("oauth_version", "1.0");
		if (timestamp < 0) {
			timestamp = System.currentTimeMillis() / 1000;
		}
		params.put("oauth_timestamp", String.valueOf(timestamp));
	}

	static OAuthEntity generateRequestToken(OAuthEntity consumer, String scope,
			String callbackUrl, long nonce, long timestamp,
			GoogleAccountsUrlGenerator urlGenerator)
			throws java.security.InvalidKeyException,
			java.security.NoSuchAlgorithmException, java.io.IOException {
		java.util.Map<String, String> params = new java.util.HashMap<String, String>();
		fillInCommonOauthParams(params, consumer, nonce, timestamp);
		params.put("oauth_callback", callbackUrl); // for installed application.
		params.put("scope", scope);
		String requestUrl = urlGenerator.getRequestTokenUrl();
		OAuthEntity token = new OAuthEntity(null, "");
		String baseString = generateSignatureBaseString("GET", requestUrl,
				params);
		String signature = generateOauthSignature(baseString,
				consumer.getSecret(), token.getSecret());
		params.put("oauth_signature", signature);
		String url = requestUrl + "?" + formatUrlParams(params);
		String response = readFromUrl(url);
		if (!StringUtils.isBlank(response)) {
			java.util.Map<String, String> responseParams = parseUrlParamString(response);
			java.util.Iterator<java.util.Map.Entry<String, String>> i = responseParams
					.entrySet().iterator();
			while (i.hasNext()) {
				java.util.Map.Entry<String, String> next = i.next();
				if (LOGGABLE) {
					logger.info(next.getKey() + "=" + next.getValue());
				}
			}
			String authorizationUrl = String.format(
					"  %s?oauth_token=%s",
					urlGenerator.getAuthorizeTokenUrl(),
					java.net.URLEncoder.encode(
							responseParams.get("oauth_token"), ENC));
			if (LOGGABLE) {
				logger.info("To authorize token, "
						+ "visit this url and follow the directions "
						+ "to generate a verification code:");

				System.out.println("authorizationUrl======" + authorizationUrl);
				logger.info(authorizationUrl);
			}
			token = new OAuthEntity(responseParams.get("oauth_token"),
					responseParams.get("oauth_token_secret"), authorizationUrl);
		}
		return token;
	}

	static OAuthEntity getAccessToken(OAuthEntity consumer,
			OAuthEntity requestToken, String oauthVerifier,
			GoogleAccountsUrlGenerator urlGenerator)
			throws java.security.InvalidKeyException,
			java.security.NoSuchAlgorithmException, java.io.IOException {
		java.util.Map<String, String> params = new java.util.HashMap<String, String>();
		fillInCommonOauthParams(params, consumer, -1, -1);
		params.put("oauth_token", requestToken.getKey());
		params.put("oauth_verifier", oauthVerifier);
		String requestUrl = urlGenerator.getAccessTokenUrl();
		String baseString = generateSignatureBaseString("GET", requestUrl,
				params);
		String signature = generateOauthSignature(baseString,
				consumer.getSecret(), requestToken.getSecret());
		params.put("oauth_signature", signature);
		String url = requestUrl + "?" + formatUrlParams(params);
		String response = readFromUrl(url);
		java.util.Map<String, String> responseParams = parseUrlParamString(response);
		java.util.Iterator<java.util.Map.Entry<String, String>> i = responseParams
				.entrySet().iterator();
		while (i.hasNext()) {
			java.util.Map.Entry<String, String> next = i.next();
			if (LOGGABLE) {
				logger.info(next.getKey() + "=" + next.getValue());
			}
		}
		OAuthEntity token = new OAuthEntity(responseParams.get("oauth_token"),
				responseParams.get("oauth_token_secret"));
		return token;
	}


	static java.util.List<String> sorted(java.util.Map<String, String> map) {
		java.util.List<String> list = new java.util.ArrayList<String>(
				map.size());
		list.addAll(map.keySet());
		java.util.Collections.sort(list);
		return list;
	}

	static String readFromUrl(String urlString) throws java.io.IOException {
		String returnString = null;
		java.net.URL url = new java.net.URL(urlString);
		URLFetchService urlFetchService = URLFetchServiceFactory
				.getURLFetchService();
		HTTPResponse response = null;
		for (int i = 0; i < 5; i++) {
			try {
				response = urlFetchService.fetch(url);
				if (LOGGABLE) {
					logger.info("read from: " + urlString);
					logger.info("response code: " + response.getResponseCode());
				}
				if (response.getResponseCode() == 200) {
					returnString = new String(response.getContent());
				}
				break;
			} catch (IOException e) {
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				// TODO: handle exception
			}
		}

		return returnString;
	}

	/**
	 * @see java.util.prefs.Base64#byteArrayToBase64(byte[], boolean)
	 */
	static String byteArrayToBase64(byte[] bytes) {
		int aLen = bytes.length;
		int numFullGroups = aLen / 3;
		int numBytesInPartialGroup = aLen - 3 * numFullGroups;
		int resultLen = 4 * ((aLen + 2) / 3);
		StringBuilder b = new StringBuilder(resultLen);

		int inCursor = 0;
		for (int i = 0; i < numFullGroups; i++) {
			int byte0 = bytes[inCursor++] & 0xff;
			int byte1 = bytes[inCursor++] & 0xff;
			int byte2 = bytes[inCursor++] & 0xff;
			b.append(intToBase64[byte0 >> 2]);
			b.append(intToBase64[(byte0 << 4) & 0x3f | (byte1 >> 4)]);
			b.append(intToBase64[(byte1 << 2) & 0x3f | (byte2 >> 6)]);
			b.append(intToBase64[byte2 & 0x3f]);
		}

		if (numBytesInPartialGroup != 0) {
			int byte0 = bytes[inCursor++] & 0xff;
			b.append(intToBase64[byte0 >> 2]);
			if (numBytesInPartialGroup == 1) {
				b.append(intToBase64[(byte0 << 4) & 0x3f]);
				b.append("==");
			} else {
				int byte1 = bytes[inCursor++] & 0xff;
				b.append(intToBase64[(byte0 << 4) & 0x3f | (byte1 >> 4)]);
				b.append(intToBase64[(byte1 << 2) & 0x3f]);
				b.append('=');
			}
		}
		return b.toString();
	}

	static final char intToBase64[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
			'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
			'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
			'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
			'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
			'8', '9', '+', '/' };
}
